contents
아파치 카프카(Apache Kafka) 는 하루에 수조 개의 이벤트를 처리할 수 있는 분산 이벤트 스트리밍 플랫폼입니다. 핵심적으로는 높은 처리량, 낮은 지연 시간, 실시간 데이터 피드를 위해 설계된 확장성 높고, 내결함성이 뛰어나며, 내구성이 강한 메시징 시스템입니다.
카프카를 회사의 데이터를 위한 중추 신경계로 생각할 수 있으며, 이를 통해 여러 다른 애플리케이션들이 안정적이고 확장 가능한 방식으로 이벤트 스트림을 발행(publish)하고 구독(subscribe)할 수 있습니다.
핵심 개념: 빌딩 블록 📜
카프카 아키텍처를 이해하려면 먼저 기본적인 용어를 이해해야 합니다.
-
이벤트 (Event 또는 메시지): 카프카 데이터의 기본 단위입니다. 키(key), 값(value), 타임스탬프, 그리고 선택적인 메타데이터 헤더로 구성된 간단한 레코드입니다.
-
토픽 (Topic): 특정 이벤트들의 스트림입니다. 토픽은 이벤트들의 로그이며, 카테고리나 피드 이름으로 생각할 수 있습니다. 예를 들어,
user_clicks토픽이나order_payments토픽이 있을 수 있습니다. -
파티션 (Partition): 토픽은 하나 이상의 파티션으로 나뉩니다. 파티션은 순서가 있고, 변경할 수 없는 이벤트 시퀀스입니다. 파티션 내의 각 이벤트에는 오프셋이라는 순차적인 ID 번호가 할당됩니다.
- 왜 필요한가? 파티션은 카프카의 병렬성과 확장성의 핵심입니다. 토픽의 파티션들을 여러 서버에 분산시킬 수 있어, 여러 컨슈머가 동시에 한 토픽에서 읽을 수 있게 해줍니다.
-
오프셋 (Offset): 파티션 내의 각 이벤트를 식별하는 고유한 순차 정수입니다. 컨슈머는 오프셋을 사용하여 로그에서의 자신의 위치(즉, 이미 읽은 메시지가 어디까지인지)를 추적합니다.
-
브로커 (Broker): 단일 카프카 서버입니다. 브로커의 역할은 프로듀서로부터 메시지를 받아 오프셋을 할당하고 디스크 저장소에 커밋하는 것입니다. 또한 컨슈머에게 데이터를 제공합니다.
-
클러스터 (Cluster): 함께 작동하는 브로커들의 그룹입니다. 카프카 클러스터는 (더 많은 브로커를 추가하여) 확장성을, (브로커 간에 데이터를 복제하여) 내결함성을 제공합니다.
주요 역할: 프로듀서, 컨슈머, 코디네이터
카프카 아키텍처는 클러스터와 상호작용하는 몇 가지 주요 역할로 구성됩니다.
1. 프로듀서 (Producers)
프로듀서는 카프카 토픽에 이벤트를 작성(발행)하는 클라이언트 애플리케이션입니다.
-
프로듀서는 이벤트를 보낼 파티션을 선택합니다. 이는 로드 밸런싱을 위해 라운드 로빈 방식으로 이루어지거나, 이벤트의 키(key) 를 기반으로 할 수 있습니다.
-
키(
user_id등)가 제공되면, 동일한 키를 가진 모든 이벤트는 항상 동일한 파티션으로 전송됩니다. 이는 특정 사용자에 대한 이벤트가 생성된 순서대로 처리됨을 보장합니다.
2. 컨슈머와 컨슈머 그룹 (Consumers and Consumer Groups)
컨슈머는 하나 이상의 토픽에서 이벤트를 읽는(구독하는) 클라이언트 애플리케이션입니다.
-
컨슈머는 파티션 내에서 데이터를 순서대로 읽습니다.
-
병렬 처리를 위해, 컨슈머는 컨슈머 그룹의 일부로 작동합니다.
-
핵심 규칙: 단일 컨슈머 그룹 내에서, 하나의 파티션은 오직 하나의 컨슈머에 의해서만 소비될 수 있습니다. 이를 통해 컨슈머 그룹이 협력하여 토픽을 소비할 수 있으며, 각 컨슈머는 파티션의 일부를 처리합니다. 이것이 카프카가 대규모 병렬 소비를 달성하는 방식입니다.
-
그룹 내 컨슈머 수가 파티션 수보다 많으면 일부 컨슈머는 유휴 상태가 됩니다. 컨슈머 수가 파티션 수보다 적으면 일부 컨슈머는 여러 파티션에서 데이터를 읽습니다.
3. 코디네이터 (ZooKeeper 또는 KRaft)
클러스터의 브로커들은 작업을 조율해야 합니다. 여기에는 어떤 브로커가 살아있는지 추적하고, 토픽 구성을 관리하며, 파티션의 리더를 선출하는 작업이 포함됩니다.
-
주키퍼 (ZooKeeper, 과거 방식): 전통적으로 카프카는 이 모든 중요한 메타데이터를 저장하기 위해 별도의 분산 코디네이션 서비스인 아파치 주키퍼를 사용했습니다. 안정적이긴 했지만, 별도의 시스템을 관리해야 하는 운영상의 복잡성이 있었습니다.
-
KRaft (현재 방식): 최신 버전의 카프카는 주키퍼를 KRaft(Kafka Raft Metadata mode) 로 대체하고 있습니다. 이 모드에서는 클러스터 메타데이터가 카프카 자체의 특별한 내부 토픽에 저장됩니다. 브로커의 일부가 "컨트롤러" 역할을 하며 Raft 합의 프로토콜을 사용하여 이 메타데이터를 관리합니다. 이는 카프카의 아키텍처를 단순화하여 배포 및 관리를 더 쉽게 만듭니다.
핵심 아키텍처 원칙 (카프카가 빠르고 복원력 있는 이유) 🚀
카프카의 놀라운 성능은 마법이 아니라, 몇 가지 의도적인 설계의 결과입니다.
-
순차 I/O 및 페이지 캐시: 카프카는 이벤트를 디스크의 변경 불가능한, 추가만 가능한(append-only) 로그에 씁니다. 디스크에 순차적으로 쓰는 것은 매우 빠른 작업이며, 종종 RAM에 무작위로 접근하는 것보다 빠릅니다. 이 설계는 운영 체제의 페이지 캐시를 적극적으로 활용합니다. 데이터는 먼저 OS의 인메모리 캐시에 기록된 후 나중에 디스크에 플러시됩니다. 컨슈머는 종종 이 인메모리 캐시에서 직접 데이터를 읽어 물리적인 디스크 읽기를 완전히 피합니다.
-
내결함성을 위한 복제: 브로커 장애 시 데이터 손실을 방지하기 위해 파티션은 클러스터 전체에 복제됩니다.
-
각 파티션에 대해 하나의 브로커가 리더(leader) 로 선출되고, 나머지는 팔로워(follower) 가 됩니다.
-
프로듀서는 항상 리더 파티션에 씁니다.
-
팔로워들은 리더로부터 데이터를 복제합니다.
-
리더 브로커가 실패하면 팔로워 중 하나가 자동으로 새로운 리더로 승격되어 다운타임을 방지합니다.
-
-
배치 처리: 프로듀서는 이벤트를 하나씩 보내지 않습니다. 배치(batch)로 모아서 단일 요청으로 브로커에 보냅니다. 마찬가지로 컨슈머도 데이터를 배치로 가져옵니다. 이는 네트워크 오버헤드를 크게 줄이고 처리량을 향상시킵니다.
-
제로 카피 (효율성): 리눅스 시스템에서 카프카는 "제로 카피" 최적화를 사용합니다. 이를 통해 데이터가 카프카 애플리케이션의 메모리 공간으로 복사되지 않고 페이지 캐시에서 네트워크 소켓으로 직접 전송될 수 있습니다. 이는 중복 데이터 복사를 피하고 CPU 사이클과 메모리를 절약합니다.
결론적으로, 카프카의 아키텍처는 불변의 로그와 같은 단순한 개념과 파티셔닝, 복제, OS 페이지 캐시 활용과 같은 강력한 기술을 결합하여, 믿을 수 없을 정도로 성능이 뛰어나면서도 높은 복원력을 가진 플랫폼을 만들어낸 분산 시스템 설계의 정수라고 할 수 있습니다.
references